{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ef11bfb0",
   "metadata": {},
   "source": [
    "# Quantum Transformer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbb20d20",
   "metadata": {},
   "source": [
    "<div style=\"border: 2px solid black; padding: 10px; text-align: center;\">\n",
    "    <img src=\"\" style=\"width: 1500px; height: auto;\" />\n",
    "</div>\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c5434b2",
   "metadata": {},
   "source": [
    "This tutorial follows the implementation of [A Hybrid Transformer Architecture with a Quantized Self-Attention Mechanism Applied to Molecular Generation](https://arxiv.org/abs/2502.19214)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "132fc719-f0fe-4b0d-a6f8-14fb6a756ae7",
   "metadata": {},
   "source": [
    "### Installation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc370be7-cd12-4b48-9507-f328aa5bf23e",
   "metadata": {},
   "source": [
    "If running locally and would like to exclusively use `pip` run the cell below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "87071629",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install cudaq==0.9.1\n",
    "%pip install rdkit==2024.9.4\n",
    "%pip install torch==2.5.1+cu121 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121\n",
    "%pip install pandas==2.2.2\n",
    "%pip install torchdata==0.10.1\n",
    "%pip install tqdm==4.67.1\n",
    "%pip install scikit-learn==1.5.1\n",
    "%pip install seaborn==0.13.2\n",
    "%pip install gdown==5.2.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4990a85-bdc7-42a2-8e2b-d219a83c6d6e",
   "metadata": {},
   "source": [
    "If you are running on Perlmutter or would like to use `conda`, install the `cuda_quantum_transformer_env.yml` file with:\n",
    "\n",
    "`conda env create -f cuda_quantum_transformer_env.yml`\n",
    "\n",
    "The `yml` file already includes the `ipykernel` installation so one it does not need to be separately installed. As per the Perlmutter [instructions](https://docs.nersc.gov/services/jupyter/how-to-guides/), activate the environment and use `ipykernel install` to set up a Jupyter kernelspec."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3eacce7",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Algorithm and Example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "563db7ca",
   "metadata": {},
   "source": [
    "#### Creating the self-attention circuits"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0dece38c",
   "metadata": {},
   "source": [
    "The objective is to integrate a quantum circuit to replace the token and positional embeddings that propagate into the query and key representations, while keeping the token and positional embeddings used for the value matrix as classical. The quantum states representing the query and keys are then used to compute an attention score through their inner product, computed with a Hadamard test. The [Hadamard test](https://en.wikipedia.org/wiki/Hadamard_test) allows one to produce an expectation value of $\\text{Re}\\bra{\\Psi}U\\ket{\\Psi}$ for some quantum state $\\Psi$ and some unitary operator $U$. With a careful choice of $U$, one can modify the Hadamard test to produce the inner product between two quantum states of choice - to yield $\\text{Re}\\braket{\\Psi_1 | \\Psi_2}$. In this work, we learn embeddings for tokens in a sequence and take advantage of this modified-Hadamard test to compute their pairwise inner products and use these as attention scores within a transformed-based generative model. \n",
    "\n",
    "The figure below shows the circuit that accomplishes the learning of token/positional embeddings, learning query and key representations, and calculation of their inner product with a modified-Hadamard test. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2ca42ec",
   "metadata": {},
   "source": [
    "<div style=\"border: 2px solid black; padding: 10px; text-align: center;\">\n",
    "    <img src=\"\" style=\"width: 800px; height: auto;\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1337607d",
   "metadata": {},
   "source": [
    "The circuit can be extended to include additional embeddings beyond token and position. In this work, we train a model with physicochemical property embeddings as well. This conditions-based quantum circuit is shown in the figure below:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eae3a468",
   "metadata": {},
   "source": [
    "<div style=\"border: 2px solid black; padding: 10px; text-align: center;\">\n",
    "    <img src=\"\" style=\"width: 800px; height: auto;\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8119e7bb",
   "metadata": {},
   "source": [
    "Since the quantum circuit both learns representations of embeddings as well as calculates their inner product, a number of input parameters to the circuit are required:\n",
    "\n",
    "- Parameters to be used in the unitary $U_{e_i}$ that evolves the quantum state representing token information for token $i$ in the pair $(i,j)$\n",
    "- Parameters to be used in the unitary $U_{p_i}$ to evolve the quantum state representing the positional encoding of token $i$\n",
    "- Parameters to be used in the unitary $U_c$ to encode and learn physicochemical embeddings in the event of a conditions-based training\n",
    "- Parameters to be used in the unitary $U_q$ that evolve the combined embedding of token $i$ into a query state\n",
    "- Parameters to be used in the unitary $U_{e_j}$ that evolves the quantum state representing token information for token $j$ in the pair $(i,j)$\n",
    "- Parameters to be used in the unitary $U_{p_j}$ to evolve  the  quantum state representing the positional encoding of token $j$\n",
    "- Parameters to be used in the unitary $U_k$ that evolve the combined embedding of token $i$ into a key state\n",
    "\n",
    "- The structure of each ansatz is the same (Ry followed by CNOT), but an integer is also passed to CUDA-Q function to specify how many layers of Ry/CNOTs are used to create the ansatz. In this work, this is set to 1.\n",
    "- The total number of qubits we wish to represent the query and key states is also passed to the function\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a38b96e4",
   "metadata": {},
   "source": [
    "Before building this circuit in CUDAQ, we first build three CUDAQ helper functions to make the implementation of the larger circuit more straightfoward. These functions are as follows (recall the structure of our ansatzes are chosen to be the same in this work):\n",
    "\n",
    "- A function to implement $U$ \n",
    "- A function to implement controlled-$U^\\dag$\n",
    "- A function to implement controlled-$U$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b2a6389c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from quantum_transformer_src.unitary_library import unitary, controlled_adjoint_unitary, controlled_unitary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58b7cd3c",
   "metadata": {},
   "source": [
    "After importing these helper functions, it is much more straightforward to build and visualize the overall circuits for learning embeddings and calculating the query/key inner product. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "05d4c2d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This quantum circuit only include token and positional encodings as shown in Figure 3\n",
    "import cudaq\n",
    "\n",
    "cudaq.set_target(\"nvidia\")\n",
    "\n",
    "@cudaq.kernel\n",
    "def build_sequence_only_circuit(\n",
    "    token_i: list[float],\n",
    "    position_i: list[float],\n",
    "    query: list[float],\n",
    "    token_j: list[float],\n",
    "    position_j: list[float],\n",
    "    key: list[float],\n",
    "    ansatz_layers: list[int],\n",
    "    num_working_qubits: list[int],\n",
    "):\n",
    "    layers = ansatz_layers[0]\n",
    "    ancilla = cudaq.qubit()\n",
    "    register = cudaq.qvector(num_working_qubits[0])\n",
    "    subsystem_size = num_working_qubits[0] // 2\n",
    "\n",
    "    h(ancilla)  # noqa: F821\n",
    "    unitary(register, token_i, subsystem_size, 0, layers)\n",
    "    unitary(register, position_i, subsystem_size, 1, layers)\n",
    "    unitary(register, query, subsystem_size, -1, layers)\n",
    "    controlled_adjoint_unitary(ancilla, register, query, subsystem_size, -1, layers)\n",
    "    controlled_adjoint_unitary(ancilla, register, position_i, subsystem_size, 1, layers)\n",
    "    controlled_adjoint_unitary(ancilla, register, token_i, subsystem_size, 0, layers)\n",
    "    controlled_unitary(ancilla, register, token_j, subsystem_size, 0, layers)\n",
    "    controlled_unitary(ancilla, register, position_j, subsystem_size, 1, layers)\n",
    "    controlled_unitary(ancilla, register, key, subsystem_size, -1, layers)\n",
    "    h(ancilla)  # noqa: F821"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f09b45c5",
   "metadata": {},
   "source": [
    "We may inspect the circuit by drawing it as well as testing it with dummy values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "305af87e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unscaled attention score: 0.32332373054859076\n",
      "Circuit:\n",
      "         ╭───╮                                                                »\n",
      "q0 : ────┤ h ├────────────────────────────────────────────────────────────────»\n",
      "     ╭───┴───┴───╮           ╭───╮╭───────────╮                          ╭───╮»\n",
      "q1 : ┤ ry(2.353) ├───●───────┤ x ├┤ ry(0.365) ├───●──────────────────────┤ x ├»\n",
      "     ├───────────┤ ╭─┴─╮     ╰─┬─╯├───────────┤ ╭─┴─╮                    ╰─┬─╯»\n",
      "q2 : ┤ ry(5.974) ├─┤ x ├──●────┼──┤ ry(5.442) ├─┤ x ├──●───────────────────┼──»\n",
      "     ├───────────┤ ╰───╯╭─┴─╮  │  ├───────────┤ ╰───╯╭─┴─╮                 │  »\n",
      "q3 : ┤ ry(4.599) ├──────┤ x ├──●──┤ ry(3.777) ├──────┤ x ├──●──────────────┼──»\n",
      "     ├───────────┤      ╰───╯╭───╮├───────────┤      ╰───╯╭─┴─╮            │  »\n",
      "q4 : ┤ ry(3.761) ├───●───────┤ x ├┤ ry(4.449) ├───────────┤ x ├──●─────────┼──»\n",
      "     ├───────────┴╮╭─┴─╮     ╰─┬─╯├───────────┴╮          ╰───╯╭─┴─╮       │  »\n",
      "q5 : ┤ ry(0.9803) ├┤ x ├──●────┼──┤ ry(0.1293) ├───────────────┤ x ├──●────┼──»\n",
      "     ├────────────┤╰───╯╭─┴─╮  │  ├───────────┬╯               ╰───╯╭─┴─╮  │  »\n",
      "q6 : ┤ ry(0.9801) ├─────┤ x ├──●──┤ ry(6.094) ├─────────────────────┤ x ├──●──»\n",
      "     ╰────────────╯     ╰───╯     ╰───────────╯                     ╰───╯     »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                         »\n",
      "──●────●────●────●────●────●────────●──────────────●─────────────●───────»\n",
      "╭─┴─╮  │    │    │    │    │        │              │             │       »\n",
      "┤ x ├──┼────┼────┼────┼────●────────┼──────────────┼─────────────┼───────»\n",
      "╰─┬─╯  │    │    │    │  ╭─┴─╮      │              │             │       »\n",
      "──┼────┼────┼────┼────●──┤ x ├──────┼──────────────┼─────────────┼───────»\n",
      "  │    │    │    │  ╭─┴─╮╰───╯      │              │             │       »\n",
      "──┼────┼────┼────●──┤ x ├───────────┼──────────────┼─────────────┼───────»\n",
      "  │    │    │  ╭─┴─╮╰───╯           │              │       ╭─────┴──────╮»\n",
      "──┼────┼────●──┤ x ├────────────────┼──────────────┼───────┤ ry(-4.449) ├»\n",
      "  │    │  ╭─┴─╮╰───╯                │       ╭──────┴──────╮╰────────────╯»\n",
      "──┼────●──┤ x ├─────────────────────┼───────┤ ry(-0.1293) ├──────────────»\n",
      "  │  ╭─┴─╮╰───╯               ╭─────┴──────╮╰─────────────╯              »\n",
      "──●──┤ x ├────────────────────┤ ry(-6.094) ├─────────────────────────────»\n",
      "     ╰───╯                    ╰────────────╯                             »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                        »\n",
      "──────●─────────────●─────────────●─────────●────●────●─────────●───────»\n",
      "      │             │       ╭─────┴──────╮  │    │    │         │       »\n",
      "──────┼─────────────┼───────┤ ry(-0.365) ├──┼────┼────┼─────────┼───────»\n",
      "      │       ╭─────┴──────╮╰────────────╯  │    │    │         │       »\n",
      "──────┼───────┤ ry(-5.442) ├────────────────┼────┼────┼─────────┼───────»\n",
      "╭─────┴──────╮╰────────────╯                │    │    │         │       »\n",
      "┤ ry(-3.777) ├──────────────────────────────┼────┼────┼─────────┼───────»\n",
      "╰────────────╯                            ╭─┴─╮  │    │         │       »\n",
      "──────────────────────────────────────────┤ x ├──┼────●─────────┼───────»\n",
      "                                          ╰─┬─╯  │  ╭─┴─╮       │       »\n",
      "────────────────────────────────────────────┼────●──┤ x ├───────┼───────»\n",
      "                                            │  ╭─┴─╮╰───╯╭──────┴──────╮»\n",
      "────────────────────────────────────────────●──┤ x ├─────┤ ry(-0.9801) ├»\n",
      "                                               ╰───╯     ╰─────────────╯»\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                        »\n",
      "───────●─────────────●─────────●────●────●────────●─────────────●───────»\n",
      "       │             │       ╭─┴─╮  │    │        │             │       »\n",
      "───────┼─────────────┼───────┤ x ├──┼────●────────┼─────────────┼───────»\n",
      "       │             │       ╰─┬─╯  │  ╭─┴─╮      │       ╭─────┴──────╮»\n",
      "───────┼─────────────┼─────────┼────●──┤ x ├──────┼───────┤ ry(-5.974) ├»\n",
      "       │             │         │  ╭─┴─╮╰───╯╭─────┴──────╮╰────────────╯»\n",
      "───────┼─────────────┼─────────●──┤ x ├─────┤ ry(-4.599) ├──────────────»\n",
      "       │       ╭─────┴──────╮     ╰───╯     ╰────────────╯              »\n",
      "───────┼───────┤ ry(-3.761) ├───────────────────────────────────────────»\n",
      "╭──────┴──────╮╰────────────╯                                           »\n",
      "┤ ry(-0.9803) ├─────────────────────────────────────────────────────────»\n",
      "╰─────────────╯                                                         »\n",
      "────────────────────────────────────────────────────────────────────────»\n",
      "                                                                        »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                   »\n",
      "──────●────────────●────────────●────────────●────────●────●────●──»\n",
      "╭─────┴──────╮╭────┴─────╮      │            │        │    │  ╭─┴─╮»\n",
      "┤ ry(-2.353) ├┤ ry(5.23) ├──────┼────────────┼────────●────┼──┤ x ├»\n",
      "╰────────────╯╰──────────╯╭─────┴─────╮      │      ╭─┴─╮  │  ╰─┬─╯»\n",
      "──────────────────────────┤ ry(1.334) ├──────┼──────┤ x ├──●────┼──»\n",
      "                          ╰───────────╯╭─────┴─────╮╰───╯╭─┴─╮  │  »\n",
      "───────────────────────────────────────┤ ry(1.142) ├─────┤ x ├──●──»\n",
      "                                       ╰───────────╯     ╰───╯     »\n",
      "───────────────────────────────────────────────────────────────────»\n",
      "                                                                   »\n",
      "───────────────────────────────────────────────────────────────────»\n",
      "                                                                   »\n",
      "───────────────────────────────────────────────────────────────────»\n",
      "                                                                   »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                   »\n",
      "──────●────────────●────────────●────────●────●────●────────●──────»\n",
      "      │            │            │        │    │    │  ╭─────┴─────╮»\n",
      "──────┼────────────┼────────────┼────────┼────┼────┼──┤ ry(2.714) ├»\n",
      "      │            │            │        │    │    │  ╰───────────╯»\n",
      "──────┼────────────┼────────────┼────────┼────┼────┼───────────────»\n",
      "      │            │            │        │    │    │               »\n",
      "──────┼────────────┼────────────┼────────┼────┼────┼───────────────»\n",
      "╭─────┴─────╮      │            │        │    │  ╭─┴─╮             »\n",
      "┤ ry(1.152) ├──────┼────────────┼────────●────┼──┤ x ├─────────────»\n",
      "╰───────────╯╭─────┴─────╮      │      ╭─┴─╮  │  ╰─┬─╯             »\n",
      "─────────────┤ ry(1.912) ├──────┼──────┤ x ├──●────┼───────────────»\n",
      "             ╰───────────╯╭─────┴─────╮╰───╯╭─┴─╮  │               »\n",
      "──────────────────────────┤ ry(3.297) ├─────┤ x ├──●───────────────»\n",
      "                          ╰───────────╯     ╰───╯                  »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                                                                           »\n",
      "─────●────────────●────────────●─────────────●────────────●────────●────●──»\n",
      "     │            │            │             │            │        │    │  »\n",
      "─────┼────────────┼────────────┼─────────────┼────────────┼────────●────┼──»\n",
      "╭────┴─────╮      │            │             │            │      ╭─┴─╮  │  »\n",
      "┤ ry(1.83) ├──────┼────────────┼─────────────┼────────────┼──────┤ x ├──●──»\n",
      "╰──────────╯╭─────┴─────╮      │             │            │      ╰───╯╭─┴─╮»\n",
      "────────────┤ ry(3.844) ├──────┼─────────────┼────────────┼───────────┤ x ├»\n",
      "            ╰───────────╯╭─────┴──────╮      │            │           ╰───╯»\n",
      "─────────────────────────┤ ry(0.8765) ├──────┼────────────┼────────────────»\n",
      "                         ╰────────────╯╭─────┴─────╮      │                »\n",
      "───────────────────────────────────────┤ ry(1.836) ├──────┼────────────────»\n",
      "                                       ╰───────────╯╭─────┴─────╮          »\n",
      "────────────────────────────────────────────────────┤ ry(2.302) ├──────────»\n",
      "                                                    ╰───────────╯          »\n",
      "\n",
      "################################################################################\n",
      "\n",
      "                    ╭───╮\n",
      "──●────●────●────●──┤ h ├\n",
      "  │    │    │  ╭─┴─╮╰───╯\n",
      "──┼────┼────┼──┤ x ├─────\n",
      "  │    │    │  ╰─┬─╯     \n",
      "──┼────┼────┼────┼───────\n",
      "  │    │    │    │       \n",
      "──●────┼────┼────┼───────\n",
      "╭─┴─╮  │    │    │       \n",
      "┤ x ├──●────┼────┼───────\n",
      "╰───╯╭─┴─╮  │    │       \n",
      "─────┤ x ├──●────┼───────\n",
      "     ╰───╯╭─┴─╮  │       \n",
      "──────────┤ x ├──●───────\n",
      "          ╰───╯          \n",
      "\n"
     ]
    }
   ],
   "source": [
    "from cudaq import spin\n",
    "import numpy as np\n",
    "\n",
    "np.random.seed(42)\n",
    "\n",
    "\n",
    "hamiltonian = spin.z(0)\n",
    "\n",
    "ansatz_layers = 1\n",
    "num_working_qubits = 6\n",
    "\n",
    "# We will test the circuit which only accepts token and positional embeddings. In this case, these registers are represented by 3 qubits each\n",
    "subsystem_size = 3\n",
    "\n",
    "\n",
    "# Create dummy angles for the ansatz\n",
    "\n",
    "# Since there is only rotation angle on each qubit in the ansatz,\n",
    "# the number of angles required for each unitary is subsystem_size*ansatz_layers for the token and position encodings\n",
    "# and num_working_qubits*ansatz_layers for the query and key, as these unitaries span the whole register\n",
    "dummy_token_i = (np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi).tolist()\n",
    "dummy_position_i = (\n",
    "    np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi\n",
    ").tolist()\n",
    "dummy_query = (\n",
    "    np.random.random(num_working_qubits * ansatz_layers) * 2 * np.pi\n",
    ").tolist()\n",
    "\n",
    "dummy_token_j = (np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi).tolist()\n",
    "dummy_position_j = (\n",
    "    np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi\n",
    ").tolist()\n",
    "dummy_key = (np.random.random(num_working_qubits * ansatz_layers) * 2 * np.pi).tolist()\n",
    "\n",
    "\n",
    "observe_result = cudaq.observe(\n",
    "    build_sequence_only_circuit,\n",
    "    hamiltonian,\n",
    "    dummy_token_i,\n",
    "    dummy_position_i,\n",
    "    dummy_query,\n",
    "    dummy_token_j,\n",
    "    dummy_position_j,\n",
    "    dummy_key,\n",
    "    [ansatz_layers],\n",
    "    [num_working_qubits],\n",
    ")\n",
    "print(\"Unscaled attention score: \" + str(observe_result.expectation()))\n",
    "\n",
    "print(\"Circuit:\")\n",
    "print(\n",
    "    cudaq.draw(\n",
    "        build_sequence_only_circuit,\n",
    "        dummy_token_i,\n",
    "        dummy_position_i,\n",
    "        dummy_query,\n",
    "        dummy_token_j,\n",
    "        dummy_position_j,\n",
    "        dummy_key,\n",
    "        [ansatz_layers],\n",
    "        [num_working_qubits],\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ca9c24c",
   "metadata": {},
   "source": [
    "The hadamard test should yield a value of 1 if the two states are the same. This can be verified by copying the dummy values for the tokens:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e7262d7c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unscaled attention score: 0.9999998277981461\n"
     ]
    }
   ],
   "source": [
    "dummy_token_i = (np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi).tolist()\n",
    "dummy_position_i = (\n",
    "    np.random.random(subsystem_size * ansatz_layers) * 2 * np.pi\n",
    ").tolist()\n",
    "dummy_query = (\n",
    "    np.random.random(num_working_qubits * ansatz_layers) * 2 * np.pi\n",
    ").tolist()\n",
    "\n",
    "dummy_token_j = dummy_token_i\n",
    "dummy_position_j = dummy_position_i\n",
    "dummy_key = dummy_query\n",
    "\n",
    "\n",
    "observe_result = cudaq.observe(\n",
    "    build_sequence_only_circuit,\n",
    "    hamiltonian,\n",
    "    dummy_token_i,\n",
    "    dummy_position_i,\n",
    "    dummy_query,\n",
    "    dummy_token_j,\n",
    "    dummy_position_j,\n",
    "    dummy_key,\n",
    "    [ansatz_layers],\n",
    "    [num_working_qubits],\n",
    ")\n",
    "print(\"Unscaled attention score: \" + str(observe_result.expectation()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d627a55",
   "metadata": {},
   "source": [
    "We can turn to an example of including additional embeddings of molecular properties by using a SMILES string as input rather than a dummy values. This work uses QM9. We can call `ensure_training_data` to download the dataset if it does not exist."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b25e1993",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 04:58:12,986 [INFO] Training data found at: dataset/qm9.csv\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Warning: May need to adjust tokenization rules if using a dataset other than QM9.\n",
      "SMILES token indices: [17, 11, 11, 4, 11, 11, 4, 18, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]\n",
      "Tokenized SMILES: ['[CLS]', 'C', 'C', '1', 'C', 'C', '1', '[EOS]', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>']\n"
     ]
    }
   ],
   "source": [
    "from quantum_transformer_src.transformer import Transformer_Dataset\n",
    "from quantum_transformer_src.train import ensure_training_data\n",
    "\n",
    "ensure_training_data(training_data=\"dataset/qm9.csv\")\n",
    "\n",
    "dataset = Transformer_Dataset(\n",
    "    data_path=\"./dataset/qm9.csv\", block_size=None\n",
    ")  # If we set block_size to None, the class will infer the block size from the largest tokenized SMILES in the dataset\n",
    "datapoint = dataset[42]\n",
    "print(f\"SMILES token indices: {datapoint[0].tolist()}\")\n",
    "tokenzied_datapoint = [dataset.itos[idx] for idx in datapoint[0].tolist()]\n",
    "print(f\"Tokenized SMILES: {tokenzied_datapoint}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "feef6098",
   "metadata": {},
   "source": [
    "We can choose our own SMILES and tokenize that as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "25d2ff69",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Indicies of triethlyamine after tokenization [17, 11, 11, 13, 1, 11, 11, 2, 11, 11, 18, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]\n",
      "Tokenized triethlyamine: ['[CLS]', 'C', 'C', 'N', '(', 'C', 'C', ')', 'C', 'C', '[EOS]', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>']\n"
     ]
    }
   ],
   "source": [
    "triethlyamine_example_indices = dataset.tokenize_smiles(\"CCN(CC)CC\")\n",
    "print(f\"Indicies of triethlyamine after tokenization {triethlyamine_example_indices}\")\n",
    "triethlyamine_example_tokenzied = [\n",
    "    dataset.itos[idx] for idx in triethlyamine_example_indices\n",
    "]\n",
    "print(f\"Tokenized triethlyamine: {triethlyamine_example_tokenzied}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d352305f",
   "metadata": {},
   "source": [
    "We will walk through an example of how angles are generated for the unitary circuits presented above to quantize the learning of embeddings and calculate an attention score.\n",
    "\n",
    "In a transformer, the indices of a tokenized sequence are used to look-up their corresponding learnable vector through an embedding matrix. One can see below that each row corresponds to a set of values for a given token. These are used as angles in the unitary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5ce65e98",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Triethlyamine token embeddings:\n",
      "tensor([[ 0.6385,  0.0583, -0.3623],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [-0.5592, -0.5694, -0.9001],\n",
      "        [ 0.3628,  1.7841, -0.5299],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [ 1.3288,  1.9595,  0.8252],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [-0.9441,  2.1235,  1.9325],\n",
      "        [-0.8048, -1.7066,  0.3602],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964],\n",
      "        [ 0.4920,  0.1895, -0.9964]], grad_fn=<EmbeddingBackward0>)\n"
     ]
    }
   ],
   "source": [
    "from torch import nn\n",
    "import torch\n",
    "\n",
    "embedding_matrix = nn.Embedding(\n",
    "    len(dataset.vocab), 3\n",
    ")  # Three is the embedding dimension, in our case the number of angles required for each unitary\n",
    "triethlyamine_token_embeddings = embedding_matrix(\n",
    "    torch.tensor(triethlyamine_example_indices)\n",
    ")\n",
    "print(\"Triethlyamine token embeddings:\")\n",
    "print(triethlyamine_token_embeddings)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd5c6ebd",
   "metadata": {},
   "source": [
    "To include embeddings of molecular properties, we can calculate the properties of this SMILES string with `RDKit` using our utility function `get_physchem_properties`. This function will output nine phyisochemical properties: molecular weight (MW), hydrogen bond acceptors (HBA), hydrogen bond donors (HBD), number of rotatable bonds (nRot), number of rings (nRing), number of heteroatoms (nHet), topological polar surface area (TPSA), LogP, and number of stereocenters (StereoCenters). We use a linear layer to change the dimensionality of this property vector into the number of angles we are using to encode it into a quantum state and scale the angles between 0 and $\\pi$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fd03bbd6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Triethylamine physicochemical properties:\n",
      "[101.193, 1, 0, 3, 0, 1, 3.24, 1.3481, 0]\n",
      "Triethylamine physicochemical embeddings:\n",
      "tensor([3.1416, 0.0000, 1.4595], grad_fn=<AddBackward0>)\n"
     ]
    }
   ],
   "source": [
    "from quantum_transformer_src.utils import get_physchem_properties, scale_to_range\n",
    "\n",
    "triethylamine_example_properties = get_physchem_properties(\"CCN(CC)CC\")\n",
    "print(\"Triethylamine physicochemical properties:\")\n",
    "print(triethylamine_example_properties)\n",
    "\n",
    "properties_linear_layer = nn.Linear(len(triethylamine_example_properties), 3)\n",
    "triethylamine_physchem_embeddings = scale_to_range(\n",
    "    properties_linear_layer(torch.tensor(triethylamine_example_properties)), 0, torch.pi\n",
    ")\n",
    "\n",
    "print(\"Triethylamine physicochemical embeddings:\")\n",
    "print(triethylamine_physchem_embeddings)\n",
    "\n",
    "# Since the physicochemical embeddings all belong to the same molecule, they will be repeated for each token\n",
    "triethylamine_physchem_embeddings = triethylamine_physchem_embeddings.unsqueeze(\n",
    "    0\n",
    ").expand(dataset.block_size, -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb183891",
   "metadata": {},
   "source": [
    "For the angles assosciated with the positional encodings, query, and key, we will randomly initialize them for the purposes of the demonstration:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "c11595dc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Positional embeddings:\n",
      "Parameter containing:\n",
      "tensor([[6.5989e-01, 7.5420e-01, 4.2289e-01],\n",
      "        [8.2363e-01, 5.5234e-01, 9.4849e-01],\n",
      "        [2.8162e-01, 1.8772e-01, 8.6570e-01],\n",
      "        [8.2593e-01, 3.0761e-01, 2.7830e-01],\n",
      "        [4.8586e-02, 7.6136e-01, 2.8384e-01],\n",
      "        [2.2277e-01, 5.9890e-01, 9.5313e-01],\n",
      "        [6.4106e-01, 7.0625e-01, 3.5667e-01],\n",
      "        [1.7864e-01, 1.3209e-01, 4.1295e-01],\n",
      "        [7.7105e-01, 5.5052e-01, 4.1810e-01],\n",
      "        [5.6610e-02, 6.3487e-02, 3.2563e-01],\n",
      "        [3.7539e-02, 1.3147e-01, 2.1980e-01],\n",
      "        [7.2200e-02, 7.9201e-01, 2.8471e-01],\n",
      "        [1.4257e-04, 4.0934e-02, 8.4356e-01],\n",
      "        [7.2111e-01, 3.4584e-01, 8.4740e-02],\n",
      "        [5.0104e-01, 4.5124e-01, 1.1003e-02],\n",
      "        [3.3948e-01, 7.7301e-01, 3.6319e-01],\n",
      "        [9.7367e-01, 7.1952e-01, 4.5530e-01],\n",
      "        [8.2390e-01, 6.4680e-02, 5.7429e-01],\n",
      "        [4.6234e-01, 1.2586e-01, 7.5449e-01],\n",
      "        [7.6591e-01, 4.3635e-01, 3.1968e-01],\n",
      "        [1.7926e-01, 5.7988e-01, 9.0953e-01],\n",
      "        [4.6715e-01, 7.2218e-01, 7.6202e-01],\n",
      "        [7.8691e-01, 3.3352e-01, 5.8979e-01],\n",
      "        [4.4789e-01, 8.3580e-01, 6.4028e-01]], requires_grad=True)\n",
      "Query angles:\n",
      "Parameter containing:\n",
      "tensor([0.0371, 0.2226, 0.8525, 0.3378, 0.7377, 0.2443], requires_grad=True)\n",
      "Key angles:\n",
      "Parameter containing:\n",
      "tensor([0.9310, 0.2740, 0.0841, 0.8621, 0.5424, 0.3166], requires_grad=True)\n"
     ]
    }
   ],
   "source": [
    "positional_embedding = nn.Parameter(torch.rand(dataset.block_size, 3))\n",
    "print(\"Positional embeddings:\")\n",
    "print(positional_embedding)\n",
    "\n",
    "query_angles = nn.Parameter(torch.rand(6))\n",
    "print(\"Query angles:\")\n",
    "print(query_angles)\n",
    "\n",
    "key_angles = nn.Parameter(torch.rand(6))\n",
    "print(\"Key angles:\")\n",
    "print(key_angles)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d1f7774",
   "metadata": {},
   "source": [
    "With all the embeddings and query/key angles prepared, we can execute an example circuit. We will calculate the pairwise attention score between the first carbon and nitrogen in the sequence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c23c2acc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unscaled attention score: -0.07270196651512496\n"
     ]
    }
   ],
   "source": [
    "# This quantum circuit includes token, positional, and physicochemical embeddings as shown in Figure 5\n",
    "@cudaq.kernel\n",
    "def build_physchem_embeddings_circuit(\n",
    "    token_i: list[float],\n",
    "    position_i: list[float],\n",
    "    physchem: list[float],\n",
    "    query: list[float],\n",
    "    token_j: list[float],\n",
    "    position_j: list[float],\n",
    "    key: list[float],\n",
    "    ansatz_layers: list[int],\n",
    "    num_working_qubits: list[int],\n",
    "):\n",
    "    layers = ansatz_layers[0]\n",
    "    ancilla = cudaq.qubit()\n",
    "    register = cudaq.qvector(num_working_qubits[0])\n",
    "    subsystem_size = num_working_qubits[0] // 3\n",
    "\n",
    "    h(ancilla)  # noqa: F821\n",
    "    unitary(register, token_i, subsystem_size, 0, layers)\n",
    "    unitary(register, position_i, subsystem_size, 1, layers)\n",
    "    unitary(register, physchem, subsystem_size, 2, layers)\n",
    "\n",
    "    unitary(register, query, subsystem_size, -1, layers)\n",
    "    controlled_adjoint_unitary(ancilla, register, query, subsystem_size, -1, layers)\n",
    "\n",
    "    controlled_adjoint_unitary(ancilla, register, position_i, subsystem_size, 1, layers)\n",
    "    controlled_adjoint_unitary(ancilla, register, token_i, subsystem_size, 0, layers)\n",
    "\n",
    "    controlled_unitary(ancilla, register, token_j, subsystem_size, 0, layers)\n",
    "    controlled_unitary(ancilla, register, position_j, subsystem_size, 1, layers)\n",
    "\n",
    "    controlled_unitary(ancilla, register, key, subsystem_size, -1, layers)\n",
    "    h(ancilla)  # noqa: F821\n",
    "\n",
    "\n",
    "triethylamine_token_i = triethlyamine_token_embeddings[1].detach().numpy().tolist()\n",
    "triethylamine_position_i = positional_embedding[1].detach().numpy().tolist()\n",
    "\n",
    "triethylamine_token_j = triethlyamine_token_embeddings[3].detach().numpy().tolist()\n",
    "triethylamine_position_j = positional_embedding[3].detach().numpy().tolist()\n",
    "\n",
    "triethylamine_physchem = triethylamine_physchem_embeddings[3].detach().numpy().tolist()\n",
    "triethylamine_query = query_angles.detach().numpy().tolist()\n",
    "triethylamine_key = key_angles.detach().numpy().tolist()\n",
    "\n",
    "\n",
    "observe_result = cudaq.observe(\n",
    "    build_physchem_embeddings_circuit,\n",
    "    hamiltonian,\n",
    "    triethylamine_token_i,\n",
    "    triethylamine_position_i,\n",
    "    triethylamine_physchem,\n",
    "    triethylamine_query,\n",
    "    triethylamine_token_j,\n",
    "    triethylamine_position_j,\n",
    "    triethylamine_key,\n",
    "    [ansatz_layers],\n",
    "    [num_working_qubits],\n",
    ")\n",
    "print(\"Unscaled attention score: \" + str(observe_result.expectation()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0dc39ad7",
   "metadata": {},
   "source": [
    "In the full implementation, all pairwise $\\left(i \\geq j \\right)$ combinations are calculated, their scores are scaled, and Softmax is applied to each row to obtain the now fully classical attention matrix. This resulting attention matrix is multiplied with a classical value matrix to complete the hybrid attention mechanism. The above workflow is depicted in the Figure (note that a measurement in the X-basis is equivalent to a Hadamard transform followed by measuring in the Z-basis as done in the Hadamard test):"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86c43257",
   "metadata": {},
   "source": [
    "<div style=\"border: 2px solid black; padding: 10px; text-align: center;\">\n",
    "    <img src=\"\n",
    "    \n",
    "    \" style=\"width: 400px; height: auto;\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad041882",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Usage"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "469874d1",
   "metadata": {},
   "source": [
    "This quantum circuit for learning embeddings and calculating attention scores is built inside of transformer and used for training. The figure below shows the full model architecture:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6333f681",
   "metadata": {},
   "source": [
    "<div style=\"border: 2px solid black; padding: 10px; text-align: center;\">\n",
    "    <img src=\"\n",
    "    \n",
    "    \" style=\"width: 400px; height: auto;\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6a6c03f",
   "metadata": {},
   "source": [
    "#### Model Training"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29b05a1e",
   "metadata": {},
   "source": [
    "The arguments for `train_transformer` are as follows:\n",
    "\n",
    "- training_data (str): Path to the training data CSV file.\n",
    "- checkpoint_dir (str): Directory to save model checkpoints.\n",
    "- checkpoint_resume_path (Optional[str]): Path to resume training from a checkpoint.\n",
    "- learning_rate (float): Learning rate for the optimizer.\n",
    "- weight_decay (float): Weight decay for the optimizer.\n",
    "- batch_size (int): Batch size for training.\n",
    "- epochs (int): Number of epochs to train.\n",
    "- save_every_n_batches (int): Frequency to save batch-level model checkpoints.\n",
    "- validation_split (float): Fraction of data used for validation.\n",
    "- device (str): Device for training, either 'cpu' or 'gpu'.\n",
    "- attn_type (str): Use classical attention or quantum attention ('classical' or 'quantum').\n",
    "- num_qubits (int): Number of working qubits in the quantum attention layer.\n",
    "- ansatz_layers (int): Number of layers in the quantum ansatz.\n",
    "- conditional_training (bool): Whether to train with physicochemical properties.\n",
    "- quantum_gradient_method (str): Quantum gradient method ('spsa' or 'parameter-shift').\n",
    "- spsa_epsilon (float): Epsilon value for SPSA optimization.\n",
    "- sample_percentage (float): Fraction of dataset used for training.\n",
    "- seed (int): Random seed for reproducibility.\n",
    "- classical_parameter_reduction (bool): Ensure the number of classical parameters is equal to - the number of quantum parameters.\n",
    "- qpu_count (int): Number of GPUs to use (-1 = all available GPUs on a node).\n",
    "\n",
    "During the training, the parameters for each epoch are saved in the specified `checkpoint_dir`, along with `most_recent_batch.pt` if the `save_every_n_batches` argument is specified.\n",
    "\n",
    "The below example runs a fully classical model where the number of parameters is equal to the number of parameters used in the quantum model. This is triggered by setting the `classical_parameter_reduction` argument to `True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "98a37bcb",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 04:59:05,466 [INFO] Note reproducability between CPU and GPU not garunteed.\n",
      "2025-02-09 04:59:05,473 [INFO] Checkpoint directory set at: ./checkpoints/classical_example/\n",
      "2025-02-09 04:59:05,499 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-09 04:59:05,511 [INFO] Using device: cuda:0\n",
      "2025-02-09 04:59:05,512 [INFO] Training data found at: ./dataset/qm9.csv\n",
      "2025-02-09 04:59:16,074 [INFO] Loaded 127109 training samples and 6689 validation samples from a total of 133798 samples (100.00% of the dataset).\n",
      "Saving training SMILES: 100%|██████████| 127109/127109 [00:40<00:00, 3149.88it/s]\n",
      "2025-02-09 04:59:56,482 [INFO] Training SMILES strings with properties saved to ./training_splits/train_dataset_2025-02-09_04-59-05.csv.\n",
      "Saving validation SMILES: 100%|██████████| 6689/6689 [00:02<00:00, 3159.62it/s]\n",
      "2025-02-09 04:59:58,619 [INFO] Validation SMILES strings with properties saved to ./validation_splits/val_dataset_2025-02-09_04-59-05.csv.\n",
      "2025-02-09 04:59:58,782 [INFO] Transformer model initialized.\n",
      "2025-02-09 04:59:58,786 [INFO] Initialized classical model with shared weights to ensure same initialization between models\n",
      "2025-02-09 05:00:05,332 [INFO] Optimizer, scaler, and loss function initialized.\n",
      "2025-02-09 05:00:05,333 [INFO] Data loaders created.\n",
      "2025-02-09 05:00:05,333 [INFO] Starting training...\n",
      "Epoch 1/3; Training Loss: 0.8361: 100%|██████████| 497/497 [00:53<00:00,  9.34it/s]\n",
      "Epoch 1/3; Validation Loss: 0.7100: 100%|██████████| 27/27 [00:02<00:00, 10.48it/s]\n",
      "Epoch 2/3; Training Loss: 0.7108: 100%|██████████| 497/497 [00:52<00:00,  9.41it/s]\n",
      "Epoch 2/3; Validation Loss: 0.6858: 100%|██████████| 27/27 [00:02<00:00, 10.38it/s]\n",
      "Epoch 3/3; Training Loss: 0.6917: 100%|██████████| 497/497 [00:52<00:00,  9.52it/s]\n",
      "Epoch 3/3; Validation Loss: 0.6686: 100%|██████████| 27/27 [00:02<00:00, 10.40it/s]\n"
     ]
    }
   ],
   "source": [
    "from quantum_transformer_src.train import train_transformer\n",
    "\n",
    "train_transformer(\n",
    "    training_data=\"./dataset/qm9.csv\",\n",
    "    checkpoint_dir=\"./checkpoints/classical_example/\",\n",
    "    checkpoint_resume_path=None,\n",
    "    learning_rate=0.005,\n",
    "    weight_decay=0.1,\n",
    "    batch_size=256,\n",
    "    epochs=3,\n",
    "    save_every_n_batches=0,\n",
    "    validation_split=0.05,\n",
    "    attn_type=\"classical\",\n",
    "    num_qubits=6,\n",
    "    ansatz_layers=1,\n",
    "    conditional_training=False,\n",
    "    quantum_gradient_method=\"spsa\",\n",
    "    spsa_epsilon=0.01,\n",
    "    sample_percentage=1.0,\n",
    "    seed=42,\n",
    "    classical_parameter_reduction=True,\n",
    "    device=\"gpu\",\n",
    "    qpu_count=-1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "290fa8a4",
   "metadata": {},
   "source": [
    "To train the model with molecular property embeddings, `conditional_training` gets set to `True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "3ed9b804",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-06 13:28:44,443 [INFO] Note reproducability between CPU and GPU not garunteed.\n",
      "2025-02-06 13:28:44,446 [INFO] Checkpoint directory set at: ./checkpoints/classical_example_conditions/\n",
      "2025-02-06 13:28:44,459 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-06 13:28:44,459 [INFO] Using device: cuda:0\n",
      "2025-02-06 13:28:44,461 [INFO] Training data found at: ./dataset/qm9.csv\n",
      "2025-02-06 13:28:54,942 [INFO] Loaded 127109 training samples and 6689 validation samples from a total of 133798 samples (100.00% of the dataset).\n",
      "Saving training SMILES: 100%|██████████| 127109/127109 [00:42<00:00, 2997.24it/s]\n",
      "2025-02-06 13:29:37,364 [INFO] Training SMILES strings with properties saved to ./training_splits/train_dataset_2025-02-06_13-28-44.csv.\n",
      "Saving validation SMILES: 100%|██████████| 6689/6689 [00:02<00:00, 2946.49it/s]\n",
      "2025-02-06 13:29:39,649 [INFO] Validation SMILES strings with properties saved to ./validation_splits/val_dataset_2025-02-06_13-28-44.csv.\n",
      "2025-02-06 13:29:39,653 [INFO] Transformer model initialized.\n",
      "2025-02-06 13:29:39,656 [INFO] Initialized classical model with shared weights to ensure same initialization between models\n",
      "2025-02-06 13:29:39,657 [INFO] Optimizer, scaler, and loss function initialized.\n",
      "2025-02-06 13:29:39,657 [INFO] Data loaders created.\n",
      "2025-02-06 13:29:39,658 [INFO] Starting training...\n",
      "Epoch 1/3; Training Loss: 1.0551: 100%|██████████| 497/497 [00:58<00:00,  8.49it/s]\n",
      "Epoch 1/3; Validation Loss: 0.6301: 100%|██████████| 27/27 [00:02<00:00, 10.28it/s]\n",
      "Epoch 2/3; Training Loss: 0.6409: 100%|██████████| 497/497 [00:53<00:00,  9.32it/s]\n",
      "Epoch 2/3; Validation Loss: 0.5996: 100%|██████████| 27/27 [00:02<00:00, 10.33it/s]\n",
      "Epoch 3/3; Training Loss: 0.5612: 100%|██████████| 497/497 [00:53<00:00,  9.34it/s]\n",
      "Epoch 3/3; Validation Loss: 0.4919: 100%|██████████| 27/27 [00:02<00:00, 10.44it/s]\n"
     ]
    }
   ],
   "source": [
    "train_transformer(\n",
    "    training_data=\"./dataset/qm9.csv\",\n",
    "    checkpoint_dir=\"./checkpoints/classical_example_conditions/\",\n",
    "    checkpoint_resume_path=None,\n",
    "    learning_rate=0.005,\n",
    "    weight_decay=0.1,\n",
    "    batch_size=256,\n",
    "    epochs=3,\n",
    "    save_every_n_batches=0,\n",
    "    validation_split=0.05,\n",
    "    attn_type=\"classical\",\n",
    "    num_qubits=6,\n",
    "    ansatz_layers=1,\n",
    "    conditional_training=True,\n",
    "    quantum_gradient_method=\"spsa\",\n",
    "    spsa_epsilon=0.01,\n",
    "    sample_percentage=1.0,\n",
    "    seed=42,\n",
    "    classical_parameter_reduction=True,\n",
    "    device=\"gpu\",\n",
    "    qpu_count=-1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a31de050",
   "metadata": {},
   "source": [
    "The quantum model can be run by switching the `attn_type` argument to `'quantum'`.\n",
    "\n",
    "Training a quantum neural network is non-trivial, insofar as backprogation is much more [computationally intensive](https://arxiv.org/abs/2305.13362) than classical models. The parameter-shift method for evaluating quantum gradients scales linearly with respect to the number of parameters, making it impractical for large circuits in the NISQ-era. Alternatively, the simultaneous perturbation stochastic approximation (SPSA) algorithm [can be leveraged to approximate the quantum gradient](https://arxiv.org/abs/2211.13981) with only a constant two evaluations per circuit. We provide code for both the parameter-shift method and the SPSA algorithm, but use SPSA in this work."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "df67639c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-06 13:32:32,712 [INFO] Note reproducability between CPU and GPU not garunteed.\n",
      "2025-02-06 13:32:32,716 [INFO] Checkpoint directory set at: ./checkpoints/quantum_example/\n",
      "2025-02-06 13:32:32,727 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-06 13:32:32,728 [INFO] Using device: cuda:0\n",
      "2025-02-06 13:32:32,729 [INFO] Training data found at: ./dataset/qm9.csv\n",
      "2025-02-06 13:32:42,288 [INFO] Loaded 127109 training samples and 6689 validation samples from a total of 133798 samples (100.00% of the dataset).\n",
      "Saving training SMILES: 100%|██████████| 127109/127109 [00:39<00:00, 3249.88it/s]\n",
      "2025-02-06 13:33:21,412 [INFO] Training SMILES strings with properties saved to ./training_splits/train_dataset_2025-02-06_13-32-32.csv.\n",
      "Saving validation SMILES: 100%|██████████| 6689/6689 [00:02<00:00, 3249.12it/s]\n",
      "2025-02-06 13:33:23,482 [INFO] Validation SMILES strings with properties saved to ./validation_splits/val_dataset_2025-02-06_13-32-32.csv.\n",
      "2025-02-06 13:33:23,484 [INFO] Transformer model initialized.\n",
      "2025-02-06 13:33:23,486 [INFO] Optimizer, scaler, and loss function initialized.\n",
      "2025-02-06 13:33:23,486 [INFO] Data loaders created.\n",
      "2025-02-06 13:33:23,486 [INFO] Starting training...\n",
      "Epoch 1/3; Training Loss: 0.8797: 100%|██████████| 497/497 [1:17:17<00:00,  9.33s/it]\n",
      "Epoch 1/3; Validation Loss: 0.7182: 100%|██████████| 27/27 [01:25<00:00,  3.17s/it]\n",
      "Epoch 2/3; Training Loss: 0.7129: 100%|██████████| 497/497 [1:19:32<00:00,  9.60s/it]\n",
      "Epoch 2/3; Validation Loss: 0.6804: 100%|██████████| 27/27 [01:26<00:00,  3.22s/it]\n",
      "Epoch 3/3; Training Loss: 0.6892: 100%|██████████| 497/497 [1:20:02<00:00,  9.66s/it]\n",
      "Epoch 3/3; Validation Loss: 0.6672: 100%|██████████| 27/27 [01:26<00:00,  3.20s/it]\n"
     ]
    }
   ],
   "source": [
    "train_transformer(\n",
    "    training_data=\"./dataset/qm9.csv\",\n",
    "    checkpoint_dir=\"./checkpoints/quantum_example/\",\n",
    "    checkpoint_resume_path=None,\n",
    "    learning_rate=0.005,\n",
    "    weight_decay=0.1,\n",
    "    batch_size=256,\n",
    "    epochs=3,\n",
    "    save_every_n_batches=0,\n",
    "    validation_split=0.05,\n",
    "    attn_type=\"quantum\",\n",
    "    num_qubits=6,\n",
    "    ansatz_layers=1,\n",
    "    conditional_training=False,\n",
    "    quantum_gradient_method=\"spsa\",\n",
    "    spsa_epsilon=0.01,\n",
    "    sample_percentage=1.0,\n",
    "    seed=42,\n",
    "    classical_parameter_reduction=True,\n",
    "    device=\"gpu\",\n",
    "    qpu_count=-1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6112bd86",
   "metadata": {},
   "source": [
    "The training curves can be visualized with the `generate_plots_from_checkpoint` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "228342cb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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2toaT09Pevfuzdy5c4mJiaFfv34lOn6rVq04ffo0o0aNwt3dHXNzc2rWrEn//v05dOgQL7/8cpHHOHDgAN9++y0vvvgiLVq0oHbt2lhaWmJhYUHNmjXp1KkTc+bMISoqitatW2vq+fv7c+jQIT777DP69u1L48aNqVGjBmZmZppr69u3LytWrODEiROlmvHazMyMuXPnEhkZyXvvvUebNm1wdnbG3NwcR0dH/P39GT58uE4SWR73pTy98sorWl8cFJzwL78GDRpw8uRJNm7cyLBhw/D19cXOzg5TU1McHR0JDAxk+PDhfP/999y4cYNXX31V5xiTJ0/m0KFD9OnTB2dnZywtLalfvz7vvvsuISEheHp6FivuqKgora7q3bp1K3ZiVBFat25NeHg4q1evZsCAAXh6emJjY4OZmRnVq1enZcuWjBs3jp9++olbt27xzDPPaNX/7LPP+OWXX+jYsSPVqlXDysqK+vXr88477xASElLipdQeVTY2NuzatYtFixbRokULbGxscHBwoEOHDvz000+sXLmS2NhYrToFZ+kvqbJ+VlaG33P6kutq1aoVufa2m5sbe/fuZffu3YwZM4aAgADs7e0xNTXF3t6egIAABg0axMKFC4mNjdWalNLW1pa///6bL7/8koEDBxIYGIibmxvm5uZYWlri4eFBjx49WLBgAWFhYSVaAlOIklIpSgWtAyKEeOjmzp2rNdGVj48PR48eLfMvfCGEqEgjRozQrMigUqkIDQ19aK3tBe3fv1+nVTA2NlbnC8ipU6cyb948zfO9e/cWu0VOPLouXrxIkyZNNMuxASxevFhnbLYQ4vEiLd1CPELeeecdrW+TY2Ji6NOnj97lQYQQorKYM2eOpqVNURSDQ2Iqi/j4eK0JIp955hlJuB8T586d47nnnuO3337TWlc+Ozub4OBgnnvuOa2Eu1q1agwcONAYoQohKhFJuoV4xHz77bdaS8j8/fffDB06VNaeFEJUWnXr1mXKlCma5xs2bODixYvGC6gICxYsICUlBVB33S04G714dOXk5LB582YGDhyIi4sLTk5OuLm5YWtry1NPPUVYWJhW+S+//FJ6mwkhpHu5EI+ihIQEFi5cSP7/3gMGDCAwMNCIUQkhROVX3O7l4vEUEhKiWWqtMA4ODixYsKDUk00KIR4tsk63EI+g6tWrM3PmTGOHIYQQ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RERERERmPYVuERERERERkUmi0B1Gdrudhx56CLvdHu5SREREJox+v4mIyEw01t9vpsBo5zsXERERERERkZCop1tERERERERkkih0i4iIiIiIiEwShW4RERERERGRSaLQHQbvvfceV199NWlpaZhMJv785z+HuyQREZFxeeyxx1i5ciXx8fEsWLCAa6+9loqKinCXJSIiMi6//vWvWbp0KQkJCSQkJHD++efz+uuvj+oaCt1h0NXVxdlnn80vf/nLcJciIiIyIbZt28Z3v/tdPv74Y/72t7/h9Xq5/PLL6erqCndpIiIiY7Zo0SI2btzIZ599xmeffcbatWu55ppr2L9/f8jX0OzlYWYymdi8eTPXXnttuEsRERGZME1NTSxYsIBt27Zx8cUXh7scERGRCZOUlMTjjz/Ohg0bQjreMsn1iIiIyCzU1tYGGH+YiIiIzAQ+n4///M//pKuri/PPPz/k8xS6RUREZEIFAgHuvvtuLrroIpYsWRLuckRERMZl7969nH/++fT29hIXF8fmzZs566yzQj5foVtEREQm1J133smePXt4//33w12KiIjIuBUVFbFr1y5aW1t5+eWX+fa3v822bdtCDt4K3SIiIjJh7rrrLl599VXee+89Fi1aFO5yRERExs1ms5Gfnw/Aueeey44dO/i3f/s3fvOb34R0vkK3iIiIjFsgEOCuu+5i8+bNbN26lZycnHCXJCIiMikCgQAulyvk4xW6w6Czs5Oqqqpgu6amhl27dpGUlERmZmYYKxMRERmb7373uzz//PO88sorxMfH09DQAEBiYiIOhyPM1YmIiIzNj370I9atW0dGRgYdHR28+OKLbN26lTfeeCPka2jJsDDYunUra9asGbT929/+Nr///e+nviAREZFxMplMQ25/5plnuOWWW6a2GBERkQmyYcMG3n77berr60lMTGTp0qXce++9XHbZZSFfQ6FbREREREREZJKYw12AiIiIiIiIyEyl0C0iIiIiIiIySRS6RURERERERCaJQreIiIiIiIjIJFHoFhEREREREZkkCt0iIiIiIiIik0ShW0RERERERGSSKHSLiIiIiIiITBKFbhEREZl2Hn74YUwmU/D1+9//PtwliYiIjIlCt4iISIToH0LP9Nq6dWu4yxUREREUukVEREREREQmjSXcBYiIiMjYzJs3j6ioqCH32Wy2Ka5GREREhqLQLSIiEqF27NhBdnZ2uMsQERGREWh4uYiIyCzQ/3nvvqBeWlrKqlWriIuLY+7cuVx11VV89tlnw17D7Xbz9NNPs27dOlJSUrDZbMyZM4fly5fzwx/+kMOHD49YQ2VlJXfffTfLly9n7ty52O120tPTWb16NY899hjd3d0jnt/U1MSdd95JZmYmdrud7Oxs7r333iHP8/v9PPvss1xxxRWkpqZis9mIjY0lMzOTr371q/zwhz/kb3/725n/w4mIiIyTKRAIBMJdhIiIiJyZyWQa0K6pqQm5p7v/uVlZWVx++eX87ne/G3Sc1Wrl5Zdf5uqrrx6wva6ujmuuuYZdu3YNe4+YmBg2bdrEjTfeOGjfz372M3784x/j9XqHPb//53n44Yd55JFHgvt++MMf8uyzz9LQ0DDovMsvv5w33nhjwGe85ZZbKC0tHfZeAOecc86IXzKIiIhMBPV0i4iIzDKHDh0KBu6YmJgB+zweDzfffPOAcOtyubjyyisHBe7Tz+3u7ubmm2/mvffeG7D9iSee4L777hsUuB0Ox6BrDOfnP/85DQ0NWCwWrFbrgH1vvvkmb7zxRrC9b9++QYE7JiaG+Pj4kO4lIiIykRS6RUREIlROTs6Qy4WF0vu9ZMkSDhw4QFdXF3//+9/JyMgI7mtra+OJJ54Itjdt2sS+ffuC7QULFrB161Y6OztpamriqquuCu7zer384Ac/CLZPnDjBj3/84wH3vuSSS9i7dy9dXV10dXWxa9cu/tt/+2/DTgrX595776WtrY0TJ07wjW98Y8C+119/Pfh+7969A/a9+uqrdHV10d7eTltbG59++ikPPfQQZ5999oj3ExERmQgK3SIiIrPQr371KwoKCgBYsWIFjz766ID9f/nLX4LvX3rppQH7HnzwQS655BJMJhPz5s3jmWeeweFwBPd/+umn1NXVAfDaa6/R3t4e3Jeens5rr73GkiVLgsPBzz77bDZt2jQg+J9u2bJlbNy4kZiYGGJjY7nnnnsG7K+urg6+j4uLG7DPbDbT9zRdQkICK1eu5OGHH2bTpk3D3k9ERGSiaPZyERGRCDXckmHz588f8TybzcZXv/rVAdvWrl07oF1WVhZ837+XG+BrX/vaoDqWLl3KJ598Ety2d+9eMjMz2b1794Bjr7/++kGhOBSn92wvWLBgQLurqyv4/uKLLyYxMZG2tjYArrrqKmJiYigqKqK4uJiVK1dy9dVXk5eXN+o6RERERks93SIiIhFqx44dNDQ0DHrt2LFjxPOSk5MHTco2b968AW23243L5QIIhtc+Q4X607f1nXP6uSP1Zo9k0aJFA9qnr0Pef17YxMRENm/ePOBe3d3d7Ny5k+eff57//t//OwUFBdx5551oPlkREZlsCt0iIiKzTHNz86Cwefz48QFtm82G3W4HjBDbX1NT06Brnr6t75w5c+YM2H6mZcWGc/rkaad/aXC6NWvWUF1dzdtvv81PfvITvvWtb3HuuediNht/+gQCAX71q1/xyiuvjKkeERGRUCl0i4iIzDJut5sPPvhgwLZ33nlnQLu4uDj4fsmSJQP2vfXWWwPax48fZ8+ePQO2lZSUAAyarGzz5s0DhoJPJovFwtq1a/nxj3/MH/7wB3bs2DFgwjWAd999d0pqERGR2UuhW0REZBb67ne/S1VVFQA7d+7kgQceGLC//4zkN9xww4B9//N//k/ee+89AoEAx48f59Zbb6Wnpye4f+XKlWRmZgJw5ZVXkpCQENx35MgRvvGNb7B///5gb3t5eTm33357cPK18dq3bx/XXnstzz///ICedY/HM2hdbo/HMyH3FBERGY4mUhMREYlQK1euHHaZrXvuuWfQDN99zGYze/bsoaCggJiYGLq7uwfsT0xM5M477wy2N2zYwL//+78HJ1RrbGzkkksuGfJci8XCL37xi2B77ty5PProo9x1113Bbe+88w5LlizB4XBgNpuDPd/33nvvKD798LxeL6+88kpw6Ljdbic+Pp729nbcbveAY1etWjUh9xQRERmOerpFREQi1PHjx/nyyy+HfHV2dg57XkZGBv/6r/8KMGRofvbZZ0lJSQlui46O5i9/+cugoeKnn+twOHj22We5+OKLB2y/88472bhx46AvCHp6eqZkqLnL5eL48eODAvdll13GP/3TP036/UVEZHZTT7eIiMgs9Itf/IIVK1bw//7f/2Pfvn1YLBYuuugiHn74Yc4999xBx2dmZvLpp5/yhz/8gf/8z/9k586dtLS04HA4yM3N5bLLLuOuu+4KDis/3b333su1117Lv//7v/Puu+9SU1NDT08P8+bNo6CggH/4h38YtAzYWBUXF/OnP/2JrVu38vHHH1NfX09TUxM+ny+4vNn69eu5+eabsVj0p5CIiEwuU0BrZYiIiMx4/Wf7zsrKora2NnzFiIiIzCIaXi4iIiIiIiIySRS6RURERERERCaJQreIiIiIiIjIJFHoFhEREREREZkkmrJTRERkFtC8qSIiIuGhnm4RERERERGRSaLQLSIiIiIiIjJJFLpFREREREREJolCt4iIiIiIiMgkUegWERERERERmSQK3SIiIiIiIiKTRKFbREREREREZJIodIuIiIiIiIhMEoVuERERERERkUny/wFYuCvO5ZKSNgAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from quantum_transformer_src.analysis import generate_plots_from_checkpoint\n",
    "\n",
    "generate_plots_from_checkpoint(\n",
    "    quantum_checkpoint_path=\"./checkpoints/quantum_example/model_epoch_3.pt\",\n",
    "    classical_equal_param_checkpoint_path=\"./checkpoints/classical_example/model_epoch_3.pt\",\n",
    "    title=\"Quantum vs Classical (eq) Learning Curves\",\n",
    "    plot_train_losses=True,\n",
    "    show_plot=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ced0c59",
   "metadata": {},
   "source": [
    "As long as a `csv` file contains a header for `SMILES` or `smiles`, other chemical datasets should be able to be trained with this model. Note the tokenization rules utilized here may not be ideal for all chemistry datasets, and can be changed from within the `Transformer_Dataset` class if necessary."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "492db02d",
   "metadata": {},
   "source": [
    "#### Generating Molecules"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb32a48d",
   "metadata": {},
   "source": [
    "We can call the `generate_smiles` function to perform inference on our trained model. \n",
    "The output is the validity, uniqueness, and novelty percentages for the generated sequences. Valid sequences are those where the resulting string can be successfully parsed by `RDKit`. Novel SMILES strings are those which do not appear in the training set.\n",
    "\n",
    "\n",
    "This function has arguments: \n",
    "- checkpoint_path (str): Path to the model checkpoint.\n",
    "- save_path (Optional[str]): Path to save the generated SMILES strings.\n",
    "- choose_best_val_epoch (bool): Choose the best validation epoch for evaluation from any previous epoch.\n",
    "- num_of_model_queries (int): Number of attempts to generate SMILES strings.\n",
    "- sampling_batch_size (int): Batch size for sampling.\n",
    "- MW (Union[float, np.float64]): Molecular weight for conditional sampling.\n",
    "- HBA (Union[float, np.float64]): Hydrogen bond acceptors for conditional sampling.\n",
    "- HBD (Union[float, np.float64]): Hydrogen bond donors for conditional sampling.\n",
    "- nRot (Union[float, np.float64]): Number of rotatable bonds for conditional sampling.\n",
    "- nRing (Union[float, np.float64]): Number of rings for conditional sampling.\n",
    "- nHet (Union[float, np.float64]): Number of heteroatoms for conditional sampling.\n",
    "- TPSA (Union[float, np.float64]): Topological polar surface area for conditional sampling.\n",
    "- LogP (Union[float, np.float64]): LogP for conditional sampling.\n",
    "- StereoCenters (Union[float, np.float64]): Number of stereocenters for conditional sampling.\n",
    "- imputation_method (str): Imputation method for missing physicochemical properties.\n",
    "- imputation_dataset_path (str): Path to the imputation dataset. Default will be set to the training \n",
    "- dataset specified by the train_id from the checkpoint.\n",
    "- dataset_novelty_check_path (str): Path to the dataset for novelty check. Default will be set to the\n",
    "- training dataset specified by the train_id from the checkpoint.\n",
    "- device (str): Device for training, either 'cpu' or 'gpu'.\n",
    "- qpu_count (int): Number of GPUs to use (-1 = all available GPUs)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c178a054",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 05:06:35,283 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-09 05:06:45,013 [INFO] Generating SMILES using model_epoch_3.pt with val_loss 0.66858810627902\n",
      "Sampling molecules: 100%|██████████| 1000/1000 [00:01<00:00, 995.66it/s] \n",
      "2025-02-09 05:06:46,069 [INFO] 588 valid molecules generated (58.80 % of sampled molecules).\n",
      "2025-02-09 05:06:46,071 [INFO] 585 unique molecules generated (99.49 % of valid molecules)\n",
      "2025-02-09 05:06:47,214 [INFO] 293 novel molecules generated (50.09 % of unique molecules)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valid: 58.80%\n",
      "Unique: 99.49%\n",
      "Novel: 50.09%\n"
     ]
    }
   ],
   "source": [
    "from quantum_transformer_src.analysis import generate_smiles\n",
    "\n",
    "valid, unique, novel = generate_smiles(\n",
    "    checkpoint_path=\"./checkpoints/classical_example/model_epoch_3.pt\",\n",
    "    save_dir=\"./generated_molecules/classical_example.csv\",\n",
    "    choose_best_val_epoch=True,\n",
    "    num_of_model_queries=1000,\n",
    "    sampling_batch_size=250,\n",
    "    device=\"gpu\",\n",
    ")\n",
    "\n",
    "\n",
    "print(f\"Valid: {valid:.2f}%\")\n",
    "print(f\"Unique: {unique:.2f}%\")\n",
    "print(f\"Novel: {novel:.2f}%\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4a39503",
   "metadata": {},
   "source": [
    "If a model was trained with molecular property embeddings, the `generate_smiles` function allows it to be sampled from conditionally and grants us the option to specify molecular properties. \n",
    "\n",
    "A model that is trained with property embeddings will always expect to be fed a complete property vector. Thus, for properties we do not specify, they are [imputed](https://scikit-learn.org/stable/modules/impute.html) from other values in - by default - the training data. By default, unspecified values are imputed with `knn` ([K-Nearest Neighbors](https://scikit-learn.org/stable/modules/impute.html#nearest-neighbors-imputation)), but the `imputation_method` argument can also be `mean`, `median`, `most_frequent`, or `multivariate`.\n",
    "\n",
    "Below is an example of two sampling experiments where molecules are sampled to have a target weight of 120 and 80, respectively."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d4180138",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 05:06:57,289 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-09 05:07:07,006 [INFO] Generating SMILES using model_epoch_3.pt with val_loss 0.4918970763683319\n",
      "2025-02-09 05:07:07,164 [INFO] Imputing missing properties using the 'knn' method.\n",
      "Sampling molecules: 100%|██████████| 1000/1000 [00:00<00:00, 1545.67it/s]\n",
      "2025-02-09 05:07:07,846 [INFO] 484 valid molecules generated (48.40 % of sampled molecules).\n",
      "2025-02-09 05:07:07,847 [INFO] 376 unique molecules generated (77.69 % of valid molecules)\n",
      "2025-02-09 05:07:08,663 [INFO] 190 novel molecules generated (50.53 % of unique molecules)\n",
      "2025-02-09 05:07:08,697 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-09 05:07:18,395 [INFO] Generating SMILES using model_epoch_3.pt with val_loss 0.4918970763683319\n",
      "2025-02-09 05:07:18,564 [INFO] Imputing missing properties using the 'knn' method.\n",
      "Sampling molecules: 100%|██████████| 1000/1000 [00:00<00:00, 1814.11it/s]\n",
      "2025-02-09 05:07:19,151 [INFO] 442 valid molecules generated (44.20 % of sampled molecules).\n",
      "2025-02-09 05:07:19,153 [INFO] 155 unique molecules generated (35.07 % of valid molecules)\n",
      "2025-02-09 05:07:19,546 [INFO] 88 novel molecules generated (56.77 % of unique molecules)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " \n",
      "Average molecular weight for molecules with MW=120: 114.95\n",
      "Average molecular weight for molecules with MW=80: 78.54\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " \n",
      "Average molecular weight for molecules with MW=120: 125.65\n",
      "Average molecular weight for molecules with MW=80: 88.82\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "generate_smiles(\n",
    "    checkpoint_path=\"./checkpoints/classical_example_conditions/model_epoch_3.pt\",\n",
    "    save_dir=\"./generated_molecules/classical_example_conditions_MW_120.csv\",\n",
    "    num_of_model_queries=1000,\n",
    "    sampling_batch_size=250,\n",
    "    device=\"gpu\",\n",
    "    MW=120,\n",
    ")\n",
    "\n",
    "MW_120_target = pd.read_csv(\n",
    "    \"./generated_molecules/classical_example_conditions_MW_120.csv\"\n",
    ")[\"MW\"].mean()\n",
    "\n",
    "generate_smiles(\n",
    "    checkpoint_path=\"./checkpoints/classical_example_conditions/model_epoch_3.pt\",\n",
    "    save_dir=\"./generated_molecules/classical_example_conditions_MW_80.csv\",\n",
    "    num_of_model_queries=1000,\n",
    "    sampling_batch_size=250,\n",
    "    device=\"gpu\",\n",
    "    MW=80,\n",
    ")\n",
    "\n",
    "MW_80_target = pd.read_csv(\n",
    "    \"./generated_molecules/classical_example_conditions_MW_80.csv\"\n",
    ")[\"MW\"].mean()\n",
    "\n",
    "print(\" \")\n",
    "print(f\"Average molecular weight for molecules with MW=120: {MW_120_target:.2f}\")\n",
    "print(f\"Average molecular weight for molecules with MW=80: {MW_80_target:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "958a7c3f",
   "metadata": {},
   "source": [
    "#### Attention Maps"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87a24b57",
   "metadata": {},
   "source": [
    "To visualize the attention map of a SMILES string, we can use the `get_attention_maps` function. We provide the function with the trained checkpoint file, the directory where the figures will be saved to, and the list of SMILES string to generate an attention map with."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "2d1ad59d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 05:07:25,521 [INFO] Quantum target set to: nvidia with QPU count: 4\n",
      "2025-02-09 05:07:35,243 [INFO] Using model_epoch_3.pt with val_loss 0.66858810627902\n"
     ]
    },
    {
     "data": {
      "image/png": "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AFQvcfP3ta99LQYNGhS//e1vY968eXHwwQdHxF/ioNOnT+/0AgEAAN5J6jO5QrlcLqddRGdo/v22tEvItG51vnuq0bajS3wbpcfHryo7dqRdQbYd1a9n2iUApKY+8SiodnzmB79Ku4QK35x0fNol7NFe/Xb/13/9V/zHf/xHbNy4MXa8408dV111VacUBgAAQOdJ3Px985vfjM997nPRp0+fOPzww6PwP+athUJB8wcAAOxzBbnPxBI3f1/+8pdj7ty5ccUVV+yLegAAANgHEt/q4ZVXXom///u/3xe1AAAAsI8kbv7+/u//Ph544IF9UQsAAECHFAq19ciCxLHPo48+Or74xS/GL3/5y/jABz4QBxxwQMXXZ86c2WnFAQAA0DkS3+ph8ODB7/5ihUK8+OKLVRe1N9zqoTpu9VAdt3qoko9fVdzqoTpu9QDkWZZv9XDhD59Nu4QKt559XNol7FHi3+6XXnppX9QBAADQYXVZyVrWkMTX/L1t+/btsXbt2njrrbc6sx4AAAD2gcTN32uvvRbnn39+HHTQQXHcccfFhg0bIuIv1/pdd911e1VEa2trXHzxxTFkyJAoFosxYMCAmDhxYqxcuXKvXg8AAOja0t7gJYsbviRu/ubMmRNPP/10/Md//EfU19fvPH766afH4sWLExewfv36aGxsjJ///Ocxb968WLNmTSxbtixOPfXUmDFjRuLXAwAAYFeJr/m77777YvHixXHyySdH4X+0uP/rf/2v+PWvf524gOnTp0ehUIjHHnssevb8/y+6P+644+K8885L/HoAAADsKnHz99///d9x2GGH7XJ827ZtFc1gR2zZsiWWLVsWc+fOrWj83ta7d++k5QEAADmQtPdgL2KfH/7wh+Pf/u3fdv7324v+zW9+M0aNGpXotV544YUol8tx7LHHJi0DAACABBJP/pqamuJv//Zv47nnnou33norbrzxxnj22Wfj0UcfjQcffDDRa719i8GkXXupVIpSqVRxbHvprTiwWEz0OgAAAHmRePJ3yimnxC9+8Yt47bXX4qijjooHHngg+vXrF48++mg0NjYmeq2hQ4dGoVCI5ubmRM9ramqKhoaGisdtN//fiV4DAADIrroae2RBofz2+K2DnnnmmRg+fHi7X7vvvvvizDPPTFTA+PHjY82aNbF27dpdrvvbunVru9f9tTf5e2mz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0y6v5lm/vTN16tTYunVr3HfffTuP/eY3v4ljjjkmSqWSWz0ksGbNmjjxxBNj48aN7peYkO/f6li/zjF16tTo1q1b3H777WmXkjk+g9WxftQizR/AHowaNSqmTp0aF154YdqlAAm88cYb0a9fv1i+fHmcfPLJaZcDkDqxT4A9+OIXvxg33nhj7NixI+1SgARuv/32OOmkkzR+AP+f7mkXAFDrJkyYEOvWrYuXX345BgwYkHY5QAcdcMABrnUG+B/EPgEAAHJA7BMAACAHNH8AAAA5oPkDAADIAc0fAABADmj+AAAAckDzB0DVCoVC3HfffWmXAQDshuYPgCgUCrt9TJ06Ne0SAYAquck7ANHS0rLz3xcvXhxXXXVVrF27duexHj16pFEWANCJTP4AiMMPP3zno6GhIQqFQsWx73//+3HUUUfFgQceGMccc0x897vf3e3rXXvttdGvX79YvXp1REQ88sgj8ZGPfCR69OgRAwYMiJkzZ8a2bdt2nj9o0KD4yle+Euedd1781V/9Vbzvfe+L2267befXt2/fHhdddFH0798/6uvrY9CgQdHU1LRP1gIAuirNHwC7de+998Yll1wSs2fPjl/96ldx4YUXxqc//en493//913OLZfLcckll8Ttt98eDz/8cJxwwgmxZs2aGDduXHziE5+IZ555JhYvXhwPP/xwXHTRRRXP/epXvxojRoyIp556KqZPnx6f+9zn4vnnn4+IiPnz58f9998fP/jBD2Lt2rXxve99LwYNGrQ/fvkA0GUUyuVyOe0iAKgdixYtiksvvTS2bt0aERGjR4+O4447rmISN2nSpNi2bVv827/9W0T85ZrBu+++O370ox/F448/HitWrIj3vve9ERExZcqU6NGjR9x66607n//www/HX//1X8e2bdt2TvLGjBmzc6JYLpfj8MMPj2uuuSamTZsWM2fOjGeffTZ+9rOfRaFQ2E8rAQBdi8kfALvV3Nwco0ePrjg2evToaG5urjh22WWXxaOPPhqrVq3a2fhFRDzxxBOxaNGiOPjgg3c+xo0bFzt27IiXXnpp53nDhw/f+e9vx043btwYERFTp06N1atXxzHHHBMzZ86MBx54YF/8UgGgS9P8AbBH75y2lcvlXY597GMfi5dffjmWL19ecXzHjh1x4YUXxurVq3c+nn766Vi3bl0cddRRO8874IADdnnPHTt2RETEiSeeGC+99FL88z//c7z++usxadKkOPvsszvzlwgAXZ7dPgHYrWHDhsXDDz8cU6ZM2XnskUceiWHDhlWcd8YZZ8TEiRPjnHPOiW7dusU//MM/RMRfGrdnn302jj766Krq6NWrV0yePDkmT54cZ599dvzt3/5tbNmyJQ455JCqXhcA8kLzB8BuXX755TFp0qQ48cQT47TTTosf//jHcc8998TPfvazXc4966yz4rvf/W6ce+650b179zj77LPjiiuuiJNPPjlmzJgRn/nMZ6Jnz57R3NwcK1asiJtuuqlDNXzta1+L/v37xwknnBB1dXVx9913x+GHHx69e/fu5F8tAHRdmj8AduvMM8+MG2+8Mf7lX/4lZs6cGYMHD45vf/vb8dGPfrTd888+++zYsWNHnHvuuVFXVxef+MQn4sEHH4wrr7wyxowZE+VyOY466qiYPHlyh2s4+OCD4/rrr49169ZFt27d4sMf/nAsXbo06upcvQAAHWW3TwAAgBzwV6YAAAA5oPkDAADIAc0fAABADmj+AAAAckDzBwAAkAOaPwAAgBzQ/AEAAOSA5g8AACAHNH8AAAA5oPkDAADIAc0fAABADmj+AAAAcuD/BS8+/Whdus1wAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-09 05:07:35,881 [INFO] Saved attention map for CCN(CC)CC at ./attention_maps/classical_example/CCN(CC)CC_attention.png\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-02-06 14:54:32,856 [INFO] Saved attention map for CCN(CC)CC at ./attention_maps/classical_example/CCN(CC)CC_attention.png\n"
     ]
    }
   ],
   "source": [
    "from quantum_transformer_src.analysis import get_attention_maps\n",
    "\n",
    "get_attention_maps(\n",
    "    checkpoint_path=\"./checkpoints/classical_example/model_epoch_3.pt\",\n",
    "    save_dir=\"./attention_maps/classical_example/\",\n",
    "    smiles_list=[\"CCN(CC)CC\"],\n",
    "    choose_best_val_epoch=True,\n",
    "    show_plots=True,\n",
    "    device=\"gpu\",\n",
    "    qpu_count=-1,\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cuda_quantum_env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
